{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a8b892c8",
   "metadata": {},
   "source": [
    "Printed and electronic copies of *Modeling and Simulation in Python* are available from [No Starch Press](https://nostarch.com/modeling-and-simulation-python) and [Bookshop.org](https://bookshop.org/p/books/modeling-and-simulation-in-python-allen-b-downey/17836697?ean=9781718502161) and [Amazon](https://amzn.to/3y9UxNb)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "foreign-pepper",
   "metadata": {},
   "source": [
    "# Optimal Baseball"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "imported-table",
   "metadata": {
    "tags": []
   },
   "source": [
    "*Modeling and Simulation in Python*\n",
    "\n",
    "Copyright 2021 Allen Downey\n",
    "\n",
    "License: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "electoral-turkey",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# install Pint if necessary\n",
    "\n",
    "try:\n",
    "    from pint import UnitRegistry\n",
    "except ImportError:\n",
    "    !pip install pint\n",
    "    \n",
    "# import units\n",
    "from pint import UnitRegistry\n",
    "units = UnitRegistry()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "formal-context",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# download modsim.py if necessary\n",
    "\n",
    "from os.path import basename, exists\n",
    "\n",
    "def download(url):\n",
    "    filename = basename(url)\n",
    "    if not exists(filename):\n",
    "        from urllib.request import urlretrieve\n",
    "        local, _ = urlretrieve(url, filename)\n",
    "        print('Downloaded ' + local)\n",
    "    \n",
    "download('https://github.com/AllenDowney/ModSimPy/raw/master/' +\n",
    "         'modsim.py')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "progressive-typing",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# import functions from modsim\n",
    "\n",
    "from modsim import *"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "plastic-trigger",
   "metadata": {
    "tags": []
   },
   "source": [
    "This chapter is available as a Jupyter notebook where you can read the text, run the code, and work on the exercises. \n",
    "Click here to access the notebooks: <https://allendowney.github.io/ModSimPy/>."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "usual-institution",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "download('https://github.com/AllenDowney/ModSimPy/raw/master/' +\n",
    "         'chap22.py')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "quantitative-montana",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# import functions from previous notebook\n",
    "\n",
    "from chap22 import params\n",
    "from chap22 import make_system\n",
    "from chap22 import slope_func\n",
    "from chap22 import event_func"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "angry-pledge",
   "metadata": {},
   "source": [
    "In the previous chapter we developed a model of the flight of a\n",
    "baseball, including gravity and a simple version of drag, but neglecting spin, Magnus force, and the dependence of the coefficient of drag on velocity.\n",
    "\n",
    "In this chapter we apply that model to an optimization problem. In general, *optimization* is a process for improving a design by searching for the parameters that maximize a benefit or minimize a cost. For example, in this chapter we'll find the angle you should hit a baseball to maximize the distance it travels. And we'll use a new function, called `maximize_scalar` that searches for this angle efficiently."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "decent-birth",
   "metadata": {},
   "source": [
    "## The Manny Ramirez Problem\n",
    "\n",
    "Manny Ramirez is a former member of the Boston Red Sox (an American\n",
    "baseball team) who was notorious for his relaxed attitude and taste for practical jokes. Our objective in this chapter is to solve the following Manny-inspired problem:\n",
    "\n",
    "> What is the minimum effort required to hit a home run in Fenway Park?\n",
    "\n",
    "Fenway Park is a baseball stadium in Boston, Massachusetts. One of its\n",
    "most famous features is the \"Green Monster\", which is a wall in left\n",
    "field that is unusually close to home plate, only 310 feet away. To\n",
    "compensate for the short distance, the wall is unusually high, at 37\n",
    "feet (see <http://modsimpy.com/wally>).\n",
    "\n",
    "Starting with `params` from the previous chapter, I'll make a new `Params` object with two additional parameters, `wall_distance` and `wall_height`, in meters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "finite-warrant",
   "metadata": {},
   "outputs": [],
   "source": [
    "feet_to_meter = (1 * units.feet).to(units.meter).magnitude\n",
    "\n",
    "params = params.set(\n",
    "    wall_distance = 310 * feet_to_meter,\n",
    "    wall_height = 37 * feet_to_meter,\n",
    ")\n",
    "\n",
    "show(params)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "exposed-diving",
   "metadata": {},
   "source": [
    "The answer we want is the minimum speed at which a ball can leave home plate and still go over the Green Monster. We'll proceed in the\n",
    "following steps:\n",
    "\n",
    "1.  For a given speed, we'll find the optimal *launch angle*, that is, the angle the ball should leave home plate to maximize its height when it reaches the wall.\n",
    "\n",
    "2.  Then we'll find the minimum speed that clears the wall, given\n",
    "    that it has the optimal launch angle."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "received-diamond",
   "metadata": {},
   "source": [
    "## Finding the Range\n",
    "\n",
    "Suppose we want to find the launch angle that maximizes *range*, that is, the distance the ball travels in the air before landing. We'll use a function in the ModSim library, `maximize_scalar`, which takes a function and finds its maximum.\n",
    "\n",
    "The function we pass to `maximize_scalar` should take launch angle in degrees, simulate the flight of a ball launched at that angle, and return the distance the ball travels along the $x$ axis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "forty-knitting",
   "metadata": {},
   "outputs": [],
   "source": [
    "def range_func(angle, params):\n",
    "    params = params.set(angle=angle)\n",
    "    system = make_system(params)\n",
    "    results, details = run_solve_ivp(system, slope_func,\n",
    "                                     events=event_func)\n",
    "    x_dist = results.iloc[-1].x\n",
    "    print(angle, x_dist)\n",
    "    return x_dist"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "exact-cigarette",
   "metadata": {},
   "source": [
    "`range_func` makes a new `System` object with the given value of\n",
    "`angle`. Then it calls `run_solve_ivp` and\n",
    "returns the final value of `x` from the results.\n",
    "\n",
    "We can call `range_func` directly like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "senior-counter",
   "metadata": {},
   "outputs": [],
   "source": [
    "range_func(45, params)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "paperback-passing",
   "metadata": {},
   "source": [
    "With launch angle 45°, the ball lands about 99 meters from home plate.\n",
    "\n",
    "Now we can sweep a sequence of angles like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "biological-evans",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "angles = linspace(20, 80, 21)\n",
    "sweep = SweepSeries()\n",
    "\n",
    "for angle in angles:\n",
    "    x_dist = range_func(angle, params)\n",
    "    sweep[angle] = x_dist"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "premium-contribution",
   "metadata": {},
   "source": [
    "Here's what the results look like."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "experienced-providence",
   "metadata": {},
   "outputs": [],
   "source": [
    "sweep.plot()\n",
    "\n",
    "decorate(xlabel='Launch angle (degrees)',\n",
    "         ylabel='Range (m)')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "conscious-blade",
   "metadata": {},
   "source": [
    "It looks like the range is maximized when the initial angle is near 40°.\n",
    "We can find the optimal angle more precisely and more efficiently using `maximize_scalar`, like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "invisible-jaguar",
   "metadata": {},
   "outputs": [],
   "source": [
    "res = maximize_scalar(range_func, params, bounds=[0, 90])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "czech-command",
   "metadata": {},
   "source": [
    "The first parameter is the function we want to maximize. The second is\n",
    "the range of values we want to search; in this case, it's the range of\n",
    "angles from 0° to 90°. \n",
    "\n",
    "The return value from `maximize_scalar` is an object that contains the\n",
    "results, including `x`, which is the angle that yielded the maximum\n",
    "range, and `fun`, which is the range when the ball is launched at the optimal angle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "vocal-nerve",
   "metadata": {},
   "outputs": [],
   "source": [
    "res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "figured-uniform",
   "metadata": {},
   "outputs": [],
   "source": [
    "res.x, res.fun"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "shaped-southeast",
   "metadata": {},
   "source": [
    "For these parameters, the optimal angle is about 41°, which yields a range of 100 m.\n",
    "Now we have what we need to finish the problem; the last step is to find the minimum velocity needed to get the ball over the wall. In the exercises at the end of the chapter, I provide some suggestions. Then it's up to you!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "temporal-extension",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "This chapter introduces a new tool, `maximize_scalar`, that provides an efficient way to search for the maximum of a function. We used it to find the launch angle that maximizes the distance a baseball flies through the air, given its initial velocity.\n",
    "\n",
    "If you enjoy this example, you might be interested in this paper: \"How to hit home runs: Optimum baseball bat swing parameters for maximum range trajectories\", by Sawicki, Hubbard, and Stronge, at <http://modsimpy.com/runs>.\n",
    "\n",
    "In the next chapter, we start a new topic: rotation!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "processed-constitution",
   "metadata": {},
   "source": [
    "## Exercises\n",
    "\n",
    "This chapter is available as a Jupyter notebook where you can read the text, run the code, and work on the exercises. \n",
    "You can access the notebooks at <https://allendowney.github.io/ModSimPy/>."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "handmade-rhythm",
   "metadata": {},
   "source": [
    "### Exercise 1\n",
    "\n",
    "Let's finish off the Manny Ramirez problem:\n",
    "\n",
    "> What is the minimum effort required to hit a home run in Fenway Park?\n",
    "\n",
    "Although the problem asks for a minimum, it is not an optimization problem.  Rather, we want to solve for the initial speed that just barely gets the ball to the top of the wall, given that it is launched at the optimal angle.\n",
    "\n",
    "And we have to be careful about what we mean by \"optimal\".  For this problem, we don't want the longest range; we want the maximum height at the point where it reaches the wall.\n",
    "\n",
    "If you are ready to solve the problem on your own, go ahead.  Otherwise I will walk you through the process with an outline and some starter code.\n",
    "\n",
    "As a first step, write an `event_func` that stops the simulation when the ball reaches the wall at `wall_distance`, which is a parameter in `params`.\n",
    "Test your function with the initial conditions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "studied-association",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "developmental-alabama",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "novel-appointment",
   "metadata": {},
   "source": [
    "Next, write a function called `height_func` that takes a launch angle, simulates the flight of a baseball, and returns the height of the baseball when it reaches the wall.\n",
    "Test your function with the initial conditions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "ignored-decrease",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "western-communist",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "located-lawsuit",
   "metadata": {},
   "source": [
    "Now use `maximize_scalar` to find the optimal angle.  Is it higher or lower than the angle that maximizes range?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "duplicate-madison",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "legislative-prospect",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "interpreted-telephone",
   "metadata": {},
   "source": [
    "The angle that maximizes the height at the wall is a little higher than the angle that maximizes range.\n",
    "\n",
    "Now, let's find the initial speed that makes the height at the wall exactly 37 feet, given that it's launched at the optimal angle. \n",
    "This is a root-finding problem, so we'll use `root_scalar`.\n",
    "\n",
    "Write an error function that takes a speed and a `System` object as parameters.  It should use `maximize_scalar` to find the highest possible height of the ball at the wall, for the given speed.  Then it should return the difference between that optimal height and `wall_height`, which is a parameter in `params`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "egyptian-shadow",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "documentary-guidance",
   "metadata": {},
   "source": [
    "Test your error function before you call `root_scalar`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "sustainable-supply",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "american-biodiversity",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "reserved-shelter",
   "metadata": {},
   "source": [
    "Then use `root_scalar` to find the answer to the problem, the minimum speed that gets the ball out of the park."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "certified-webster",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "silver-bernard",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "absent-encoding",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "approved-fiction",
   "metadata": {},
   "source": [
    "And just to check, run `error_func` with the value you found."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "thick-jungle",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solution goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "simple-steps",
   "metadata": {},
   "source": [
    "## Under the Hood\n",
    "\n",
    "`maximize_scalar` uses a SciPy function called `minimize_scalar`, which provides several optimization methods.  By default, it uses `bounded`, a version of Brent's algorithm that is safe in the sense that it always uses values within the bounds you provide (including both ends).\n",
    "You can read more about it at <http://modsimpy.com/minimize>)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dense-study",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
